package hn.sigit.logic.queries.impl;

import hn.sigit.dao.hnd.administrative.HND_MunicipalTransactionDAO;
import hn.sigit.dao.hnd.administrative.HND_TransactionLogDAO;
import hn.sigit.dao.hnd.ladmshadow.ParcelDAO;
import hn.sigit.logic.general.GeneralHelper;
import hn.sigit.logic.queries.TransactionQueryHelper;
import hn.sigit.model.commons.IParcel;
import hn.sigit.model.commons.IRRR;
import hn.sigit.model.commons.IResponsibility;
import hn.sigit.model.commons.IRestriction;
import hn.sigit.model.commons.IRight;
import hn.sigit.model.commons.ISpatialZone;
import hn.sigit.model.hnd.administrative.HND_ActivityType;
import hn.sigit.model.hnd.administrative.HND_MunicipalTransaction;
import hn.sigit.model.hnd.administrative.HND_SpatialZoneInTransaction;
import hn.sigit.model.hnd.administrative.HND_TransactionLog;
import hn.sigit.model.hnd.cadastre.HND_Layer;
import hn.sigit.model.ladm.administrative.LA_AdministrativeSource;

import java.util.ArrayList;
import java.util.List;

import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

import org.apache.commons.lang.StringEscapeUtils;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Destroy;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;

import com.vividsolutions.jts.geom.Geometry;

@Stateful
@Name(TransactionQueryHelper.NAME)
@Scope(value=ScopeType.CONVERSATION)
@AutoCreate
public class TransactionQueryHelperImpl extends QueryHelperImpl<HND_MunicipalTransaction> implements TransactionQueryHelper {
	private static final long serialVersionUID = -7796590620092116712L;
	
	private List<HND_TransactionLog> transactionLogs;
	
	private List<ISpatialZone> originatedSpatialZones;
	private List<ISpatialZone> requestedSpatialZones;
	private List<ISpatialZone> neighborSpatialZones;
	private List<ISpatialZone> neighborNeighborSpatialZones;
	private List<ISpatialZone> inCourseSpatialZones;
	
	private List<IRRR> spatialZoneRRRs;
	private List<IRight> spatialZoneRights;
	private List<IRestriction> spatialZoneRestrictions;
	private List<IResponsibility> spatialZoneResponsibilities;
	private List<LA_AdministrativeSource> fileSources;
	
	private ISpatialZone lastSpatialZone;
	private int lastMapScriptState;
	private int lastMapOptionsState;
	private String mapScript;
	private String mapOptions;
	
	@In
	private GeneralHelper generalHelper;
	

	@Override
	public List<HND_MunicipalTransaction> getQueryResultList() {
		try {
			if (queryResultList == null && getSearchText() != null) {
				queryResultList = new ArrayList<HND_MunicipalTransaction>();
				if (getSearchSelector().equals("0")) {
					//search by transaction presentation number
					HND_MunicipalTransaction transaction = HND_MunicipalTransactionDAO.loadRequestByPresentationId( Long.parseLong(getSearchText()) );
					if (transaction != null)
						queryResultList.add( transaction );
				}
			}
		}
		catch (Throwable t) {
			FacesContext.getCurrentInstance().addMessage(
					"",
					new FacesMessage(
							FacesMessage.SEVERITY_ERROR,
							t.getMessage(),
							""
					)
			);
			t.printStackTrace();
		}
		
		return queryResultList;
	}

	@Override
	public HND_MunicipalTransaction getSelected() {
		return selected;
	}

	@Override
	public void setSelected(HND_MunicipalTransaction selected) {
		if (this.selected != selected) {
			this.selected = selected;
			this.fileSources = null;
			this.mapOptions = null;
			this.mapScript = null;
			this.transactionLogs = null;
			this.requestedSpatialZones = null;
			this.neighborSpatialZones = null;
			this.neighborNeighborSpatialZones = null;
			this.inCourseSpatialZones = null;
		}
	}
	
	@Override
	public List<LA_AdministrativeSource> getFileSources() {
		if (fileSources == null) {
			//TODO: we could add an ordering criterion
			fileSources = new ArrayList<LA_AdministrativeSource>();
			for (LA_AdministrativeSource source : getSelected().getSources())
				fileSources.add(source);
		}
		
		return fileSources;
	}
	
	@Override
	public List<HND_TransactionLog> getTransactionLogs() {
		if (transactionLogs == null) {
			transactionLogs = HND_TransactionLogDAO.loadTransactionLogsByPresentationId(getSelected().getPresentationId());
		}
		return transactionLogs;
	}
	
	
	@Override
	public List<ISpatialZone> getOriginatedSpatialZones() {
		if (originatedSpatialZones == null) {
			originatedSpatialZones = new ArrayList<ISpatialZone>();
			if (getSelected() != null)
				for (ISpatialZone spatialZone : getSelected().getOriginatedSpatialZones())
					originatedSpatialZones.add(spatialZone);
		}
		return originatedSpatialZones;
	}

	@Override
	public List<ISpatialZone> getRequestedSpatialZones() {
		if (requestedSpatialZones == null) {
			requestedSpatialZones = new ArrayList<ISpatialZone>();
			if (getSelected() != null)
				addSpatialZones(requestedSpatialZones, getSelected(), 0);
		}
		return requestedSpatialZones;
	}

	@Override
	public List<ISpatialZone> getNeighborSpatialZones() {
		if (neighborSpatialZones == null) {
			neighborSpatialZones = new ArrayList<ISpatialZone>();
			if (getSelected() != null)
				addSpatialZones(neighborSpatialZones, getSelected(), 1);
		}
		return neighborSpatialZones;
	}

	@Override
	public List<ISpatialZone> getNeighborNeighborSpatialZones() {
		if (neighborNeighborSpatialZones == null) {
			neighborNeighborSpatialZones = new ArrayList<ISpatialZone>();
			if (getSelected() != null)
				addSpatialZones(neighborNeighborSpatialZones, getSelected(), 2);
		}
		return neighborNeighborSpatialZones;
	}
	
	@Override
	public List<ISpatialZone> getInCourseSpatialZones() {
		if (inCourseSpatialZones == null) {
			inCourseSpatialZones = new ArrayList<ISpatialZone>();
			if (getSelected() != null && !getSelected().isCompleted()) {
				for (ISpatialZone sz : ParcelDAO.loadParcelsAfterTransactionByPresentationId(getSelected().getPresentationId()))
					inCourseSpatialZones.add(sz);
			}
		}
		return inCourseSpatialZones;
	}

	@Override
	public List<IRight> getSpatialZoneRights(ISpatialZone spatialZone) {
		if (spatialZone != lastSpatialZone) {
			spatialZoneRights = new ArrayList<IRight>();
			spatialZoneRRRs = null;
			for (IRRR rrr : getSpatialZoneRRRs(spatialZone))
				if (rrr instanceof IRight)
					spatialZoneRights.add((IRight) rrr);
		}
		return spatialZoneRights;
	}
	
	@Override
	public List<IRestriction> getSpatialZoneRestrictions(ISpatialZone spatialZone) {
		if (spatialZone != lastSpatialZone) {
			spatialZoneRestrictions = new ArrayList<IRestriction>();
			spatialZoneRRRs = null;
			for (IRRR rrr : getSpatialZoneRRRs(spatialZone))
				if (rrr instanceof IRestriction)
					spatialZoneRestrictions.add((IRestriction) rrr);
		}
		return spatialZoneRestrictions;
	}
	
	@Override
	public List<IResponsibility> getSpatialZoneResponsibilities(ISpatialZone spatialZone) {
		if (spatialZone != lastSpatialZone) {
			spatialZoneResponsibilities = new ArrayList<IResponsibility>();
			spatialZoneRRRs = null;
			for (IRRR rrr : getSpatialZoneRRRs(spatialZone))
				if (rrr instanceof IResponsibility)
					spatialZoneResponsibilities.add((IResponsibility) rrr);
		}
		return spatialZoneResponsibilities;
	}

	@Override
	public boolean isParcelInstance(ISpatialZone spatialZone) {
		return spatialZone instanceof IParcel;
	}
	
	
	private List<IRRR> getSpatialZoneRRRs(ISpatialZone spatialZone) {
		if (spatialZoneRRRs == null) {
			spatialZoneRRRs = new ArrayList<IRRR>();
			for (IRRR rrr : spatialZone.getProperty().getRrr())
				spatialZoneRRRs.add(rrr);
		}
		
		return spatialZoneRRRs;
	}
	
	private void addSpatialZones(List<ISpatialZone> listToAdd, HND_MunicipalTransaction transaction, int neighborLevel) {
		if (listToAdd == null || transaction == null) return;
		
		for (HND_SpatialZoneInTransaction szit : transaction.getSpatialZoneInTransactions())
			if (szit.getNeighborLevel() == neighborLevel)
				listToAdd.add(szit.getSpatialZone());
	}

	
	@Override
	public String mapOptions(int state) {
		if (mapOptions == null || state != lastMapOptionsState) {
			mapOptions = null;
			lastMapOptionsState = state;
			double x1, y1, x2, y2;

			List<Geometry> envelopeList = new ArrayList<Geometry>();

			if (state % 2 == 0) { //state before transaction
				for (ISpatialZone sz : getRequestedSpatialZones())
					envelopeList.add(sz.getShape().getEnvelope());
			}
			else { //state after transaction
				if (getSelected().isCompleted()) {
					for (ISpatialZone sz : getOriginatedSpatialZones())
						envelopeList.add(sz.getShape().getEnvelope());
				}
				else {
					for (ISpatialZone sz : getInCourseSpatialZones())
						envelopeList.add(sz.getShape().getEnvelope());
				}
			}
			
			if (envelopeList.size() > 0) {
				double[] dd = generalHelper.calcZoomEnvelope(envelopeList);
				x1 = dd[0]; y1 = dd[1]; x2 = dd[2]; y2 = dd[3];
	
				mapOptions = String.format("{ controls:[], " +
						"maxExtent: new OpenLayers.Bounds(%f, %f, %f, %f), " +
						"maxResolution: 26.280468750000182, " +
						"projection: new OpenLayers.Projection('%s'), " +
						"displayProjection: new OpenLayers.Projection('%s'), " +
						"units: 'm' }",
						
						x1, y1, x2, y2,
						generalHelper.getWorkingSRS(),
						generalHelper.getWorkingSRS());
			}
		}
		
		return mapOptions;
	}

	@Override
	public String mapScript(int state, String mapControlName) {
		if (mapScript == null || state != lastMapScriptState) {
			mapScript = null;
			lastMapScriptState = state;
			
			StringBuffer sb = new StringBuffer(
					"var vector_style = new OpenLayers.Style({'strokeColor': '#ff0000','strokeWidth': 3});" +
					"var vector_style_map = new OpenLayers.StyleMap({'default': vector_style});" +
					"vlayer.styleMap = vector_style_map;" +
					
					"var parcel_style = new OpenLayers.Style({" +
					"	'fillColor': '#669933'," +
					"	'fillOpacity': .8," +
					"	'graphicName': 'square'," +
					"	'label': '${label}'," +
					"	'pointRadius': 16," +
					"	'strokeColor': '#aaee77'," +
					"	'strokeDashstyle': 'solid'," +
					"	'strokeWidth': 4" +
					"});" +
					"" +
					"var parcel_style_map = new OpenLayers.StyleMap({'default': parcel_style});" +
					"" +
					"var parcel_symbolizer_lookup = {" +
					"	'0': {" +
					"		'fillColor': '#669933', 'fillOpacity': .5, 'strokeColor': '#aaee77', 'strokeWidth': 3, 'pointRadius': 8, 'label': '${label}'" +
					"	}," +
					"	'1': {" +
					"		'fillColor': '#FF0000', 'fillOpacity': .5, 'strokeColor': '#aaee77', 'strokeWidth': 3, 'pointRadius': 8, 'label': '${label}'" +
					"	}" +
					"};" +
					"parcel_style_map.addUniqueValueRules('default', 'neighborLevel', parcel_symbolizer_lookup);" +
					"parcelFeaturesLayer.styleMap = parcel_style_map;" +
					"" +
					"var allSZGml, spatialZoneWKT, geom;"
			);
	
			String allSZGml;
			if (state % 2 == 0) { //state before transaction
				allSZGml = generalHelper.getSpatialZonesGML(getRequestedSpatialZones(), 0);
				sb.append(mapScriptUtil(allSZGml));
			}
			else { //state after transaction
				if (getSelected().isCompleted())
					allSZGml = generalHelper.getSpatialZonesGML(getOriginatedSpatialZones(), 0);
				else
					allSZGml = generalHelper.getSpatialZonesGML(getInCourseSpatialZones(), 0);
				sb.append(mapScriptUtil(allSZGml));
			}
			
			for (HND_Layer layer : generalHelper.getLayerList())
				if (!layer.getFullQualifiedName().equals(generalHelper.getPrimaryWMSLayer()))
					sb.append(generalHelper.drawLayerJS(mapControlName, layer));
			
			mapScript = sb.toString();
		}

		return mapScript;
	}
	
	private String mapScriptUtil(String gml) {
		return String.format("allSZGml = '%s';" +
				"geom = new OpenLayers.Format.GML().read(allSZGml);" +
				"parcelFeaturesLayer.addFeatures(geom);",
				
				StringEscapeUtils.escapeJavaScript(StringEscapeUtils.escapeJavaScript(gml)));
	}
	
	@Override
	public boolean isApprovedLocally() {
		if (getSelected() != null) {
			HND_ActivityType activity = getSelected().getCurrentActivity();
			
			return activity == HND_ActivityType.DELIVER_FOR_EXTERNAL_APPROVAL
					|| activity == HND_ActivityType.EXTERNAL_APPROVAL
					|| activity == HND_ActivityType.END;
		}
		return false;
	}
	
	@Override
	public String claimingUser(HND_TransactionLog tl) {
		String val = "--";
		if (tl.getClaimingUser() != null)
			val = tl.getClaimingUser().getUserName();
		
		return val;
	}

	@Override
	@Destroy @Remove
	public void destroy() {}
	
}
